﻿/**
 * An implementation of a UI that uses the IVIEW interface so that it can register
 * itself with a CAViewController. Some methods are just forwarding the events
 * that are triggered by the System.Windows.Forms.Control events and torher adjust to the signature
 * of the methods specified by the IVIEW interface.
 * 
 * A 'CAControl' is not to be confused with a type of 'IController'. A CAControl is a .net control. 
 * 
 * Author: Anders Høst | ahkj@itu.dk
 * Date: 05 Dec 2010
 */

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
using AutomaTones.Model.Automaton_Model;
using AutomaTones.Model.Automaton_Model.RuleLib;
using AutomaTones.Model.MIDI.Mappers;
using AutomaTones.Model.Tools;
using Midi;

namespace AutomaTones.View.GUI_View {
    public partial class CaControl : UserControl {
        private const int offSetX = 5;
        private const int offSetY = 5;
        private const int _cellMargin = 1;
        private double _cellSize = 10;
        private int _columns = 28;
        private int _rows = 28;
        private readonly Assembly _files;

        //Painting graphics
        private SolidBrush _sb;
        private Rectangle[,] _rect;

        public CaControl() {
            InitializeComponent();
            //Program.SetVisualStyles();

            // Gets all the embedded resources and collects them in this assembly.
            _files = Assembly.GetExecutingAssembly();
            
            //playButtonPic.Image = new Bitmap(PlayButton);
            playButtonPic.Image = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.PlayButton.png"));
            pauseButtonPic.Image = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.PauseButton.png"));
            FFpictureBox1.Image = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.FastForward.png"));
            BackwardspictureBox2.Image = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.Backwards.png"));
            soundIconPictureBox1.Image = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.soundIcon.png"));
            this.BackgroundImage = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.BackgroundGreen1.png"));
           CustomGrid = new State[_columns, _rows];
           _sb = new SolidBrush(Color.Empty);
           SetupPreferredGrid(CustomGrid);
        }

        /// <summary>
        /// Setup for painting grid graphics. Should be recall when a grid is resized.
        /// </summary>
        private void InitGraphics() {
            SetOptimalCellSize(_columns, _rows);
            _rect = new Rectangle[_columns,_rows];
            for (int i = 0; i < _columns; i++) {
                for (int j = 0; j < _columns; j++)
                {
                    _rect[i,j] = new Rectangle((int)(_cellMargin + (i * _cellSize) + offSetX),
                     (int)(_cellMargin + (j * _cellSize) + offSetY),
                     (int)_cellSize - _cellMargin,
                     (int)(_cellSize - _cellMargin));
                }
            }
         }

        public void Init() {
            InitGraphics();
            PaintBackground();
            ResetGrid();
        }

        /// <summary>
        /// A custom grid is used for mouse based interaction with the system.
        /// </summary>
        public State[,] CustomGrid { get; set; }

        /// <summary>
        /// Set a new grid with the desired dimensions. 
        /// </summary>
        /// <param name="dimension"></param>
        /// <param name="rows"></param>
        public void SetGridDimension(int columns, int rows) {
            this._columns = columns;
            this._rows = columns;
            InitGraphics();
            PaintBackground();
            for (int i = 0; i < columns; i++)
                for (int j = 0; j < rows; j++)
                    SetCell(i, j, new State());
        }

        /// <summary>
        /// Set a single cell on the grid displayed. The method reacts to this if the cell is in the range of this
        /// particualr implementation of a GUi. It's assumed that the controller handles system logic of what cells can be 
        /// changed when. 
        /// </summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetCell(int xPos, int yPos, State cell)
        {
            Graphics graphics = gridPanel.CreateGraphics();
                graphics = gridPanel.CreateGraphics();
                //check if the coordinates are inside the grid
                if (_columns < xPos || _rows < yPos)
                    return;

                switch (cell.CurrentState)
                {
                    case 0:
                        {
                            _sb.Color = Color.FromArgb(255, Color.White);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    case 1:
                        {
                            _sb.Color = Color.FromArgb(255, Color.Red);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    case 2:
                        {
                            _sb.Color = Color.FromArgb(255, Color.White);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                            _sb.Color = Color.FromArgb(120, Color.Red);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    case 3:
                        {
                            _sb.Color = Color.FromArgb(255, Color.Orange);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    case 4:
                        {
                            _sb.Color = Color.FromArgb(255, Color.White);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                            _sb.Color = Color.FromArgb(120, Color.Orange);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    case 5:
                        {
                            _sb.Color = Color.FromArgb(255, Color.Green);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    case 6:
                        {
                            _sb.Color = Color.FromArgb(255, Color.White);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                            _sb.Color = Color.FromArgb(120, Color.Green);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                    default:
                        {
                            _sb.Color = Color.FromArgb(255, Color.White);
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                            _sb.Color = Color.FromArgb(120, Color.Black);
                            ;
                            graphics.FillRectangle(_sb, _rect[xPos, yPos]);
                        }
                        break;
                }
        }

        /// <summary>
        /// Mouse click on the grid panel. Based on the mouse click position
        /// it calculates the colum and rown that was chosen.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GridPanelClick(object sender, MouseEventArgs e) {
            int mouseX = e.X - offSetX;
            int column = (int)(mouseX / (_cellSize));

            int mouseY = e.Y - offSetY;
            int row = (int)(mouseY / (_cellSize));

            if (e.Button == System.Windows.Forms.MouseButtons.Right) {
                ShowCaPatterns(e.X, e.Y, column,row);
                } else
                OnCellClickEvent.Invoke(column, row);
        }


        public void SetGrid(State[,] grid) {
            for (int i = 0; i < grid.GetLength(0); i++) {
                for (int j = 0; j < grid.GetLength(1); j++) {
                    SetCell(i, j, grid[i, j]);
                }
            }
        }

        public void ResetGrid(){
            _columns = CustomGrid.GetLength(0);
            _rows = CustomGrid.GetLength(1);
            SetOptimalCellSize(_columns, _rows);
            PaintBackground();
            SetGrid(CustomGrid);
        }

        /// <summary>
        /// Determines the best cell size so coulms and rows will fit on the panel. 
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="rows"></param>
        private void SetOptimalCellSize(int columns, int rows) {
            if (gridPanel.Width / columns < gridPanel.Height / rows) {
                _cellSize = (gridPanel.Width / columns) - ((2*offSetX) / columns);
            } else {
                _cellSize = (gridPanel.Height / rows) - ((2*offSetX) / columns);
            }
        }

        /// <summary>
        /// Set up the preferred grid with a default state. It is reused for redrawing the gridpanel
        /// to this size and we don't want to rerun this algorithm everytime we do that. 
        /// </summary>
        /// <param name="grid"></param>
        private void SetupPreferredGrid(State[,] grid) {
            for (int i = 0; i < grid.GetLength(0); i++) {
                for (int j = 0; j < grid.GetLength(1); j++) {
                    grid[i, j] = new State();
                }
            }
        }


        private void PaintBackground(){
            Graphics g = gridPanel.CreateGraphics();
            g.FillRectangle(new SolidBrush(gridPanel.BackColor), 0, 0, gridPanel.Width, gridPanel.Height);
        }

        /// <summary>
        /// Diplay info and help messages sent from the system.
        /// </summary>
        /// <param name="message"></param>
        public void SystemMessage(string message) {
            infoLabel.Text = message.ToString();
        }

        /// <summary>
        /// Puts a small name png on UI. To extends this method add .png to the graphics folder with
        /// the signature ca'thisControlNumber'.png
        /// </summary>
        /// <param name="thisControlNumber"></param>
        public void SetName(int thisControlNumber) {
            CAnamePictureBox1.Image = new Bitmap(_files.GetManifestResourceStream("AutomaTones.Graphics.Ca"+thisControlNumber+".png"));
        }

        /// <summary>
        /// Set the different input modes to a combobox.
        /// </summary>
        /// <param name="list"></param>
        public void DisplayInputModes(List<InputMode> list){
            foreach (InputMode im in list){
                if (!inputModeComboBox.Items.Contains(im))
                    inputModeComboBox.Items.Add(im);
            }
        }

        public void DisplayMappers(List<Mapper> list){
            foreach (Mapper m in list) {
                if (!MapperComboBox.Items.Contains(m))
                    MapperComboBox.Items.Add(m);
            }
        }

        /// <summary>
        /// Set the list output channels to a combobox
        /// </summary>
        /// <param name="channels"></param>
        public void DisplayChannels(List<Channel> list) {
            foreach (Channel ch in list) {
                if (!ChannelComboBox.Items.Contains(ch))
                    ChannelComboBox.Items.Add(ch);
            }
        }

        /// <summary>
        /// Sets the list of choosable CARules to a combobox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void DisplayCATypes(List<CAType> list){
            foreach (CAType caType in list) {
                if (!CATypeComboBox.Items.Contains(caType))
                    CATypeComboBox.Items.Add(caType);
            }
        }

        //Ok button was clicked. Used for custom user input control
        private void okButton_Click(object sender, EventArgs e) {
            OnOkButtonClickEvent.Invoke();
        }

        //The play button was clicked
        private void playButtonPic_Click(object sender, EventArgs e) {
            OnPlayButtonClickEvent.Invoke();
        }

        //This method is called when the volume changes on the 
        //scrolle bar. It delegates this volue.
        private void volumeScrollBar_CursorChanged(object sender, EventArgs e) {
            HScrollBar hsc = (HScrollBar)sender;
            OnVolumeChangeEvent.Invoke(hsc.Value);
        }

        //Load button was clicked on scrren, delegate this.
        private void LoadButton_Click(object sender, EventArgs e) {
            OpenFileDialog ofg = new OpenFileDialog();
            ofg.Filter = "Text files (*.*)|*.*|All files (*.*)|*.*";
            if (ofg.ShowDialog() == DialogResult.OK)
               OnLoadButtonClickEvent.Invoke(ofg.FileName);
        }

        private void PauseButton_Click(object sender, EventArgs e) {
            OnPauseButtonClickEvent.Invoke();
        }

        /// <summary>
        /// Displays a usercontrol for setting a ca pattern on the grid.
        /// </summary>
        /// <param name="mouseX"></param>
        /// <param name="y"></param>
        private void ShowCaPatterns(int mouseX, int mouseY, int column, int rows)
        {
             UserControl caList = new UserControl();
            if(mouseX + caList.Width > gridPanel.Width)
                caList.Location = new Point(gridPanel.Width - caList.Width, mouseY);
            else if(mouseY + 30 > gridPanel.Height)
                caList.Location = new Point(mouseX,gridPanel.Height-30);
            else if(mouseX - (caList.Width / 2) < 0)
                caList.Location = new Point(30, mouseY - 3);
            else
                caList.Location = new Point(mouseX - (caList.Width / 2), mouseY -3);
            ComboBox cb = new ComboBox();
            bool entered = false;
            cb.Text = "Select pattern...";
            cb.SelectedItem = Patterns[0];
            foreach (GameOfLife.GofPattern ca in this.Patterns)
                    cb.Items.Add(ca);

            //anonymous listener for item changed
            cb.SelectedIndexChanged += (s1, e1) => {
                OnApplyCaPatternEvent(((GameOfLife.GofPattern) ((ComboBox) s1).SelectedItem), column, rows);
                this.Controls.Remove(caList);
                caList.Dispose();
            };

            cb.MouseEnter += (s1, e1) => {
                entered = true;
            };

            cb.MouseLeave += (s1, e1) => {
                if (entered)
                {
                    this.Controls.Remove(caList);
                    caList.Dispose();
                    this.Update();
                    SetGridDimension(_columns, _rows);
                }


            };

            caList.Height = cb.Height;
            caList.Width = cb.Width;
            caList.Controls.Add(cb);
            this.gridPanel.Controls.Add(caList);
        }

        //Save button was clicked on sceen, get a name for the CA and delegate this. Basically
        // a UserControl that takes use of this.Usercontrol
        private void saveButton_Click(object sender, EventArgs e)
        {
            GridResizer gr = new GridResizer(ResizeGrid);
            UserControl inputForm = new UserControl();
            inputForm.Location = new Point(this.saveButton.Location.X, saveButton.Location.Y);

            //for name input
            TextBox tb = new TextBox();
            tb.Width = this.inputModeComboBox.Width;
            tb.Height = 25;
            inputForm.Height = tb.Height;
            inputForm.Width = tb.Width;

            //save button
            Button saveBtn = new Button();
            saveBtn.Text = "Save";
            saveBtn.Location = new Point(0,tb.Height);
            saveBtn.UseVisualStyleBackColor = true;

            inputForm.Height = tb.Height + saveBtn.Height; //reasonable height

            //cancel button
            Button cancelBtn = new Button();
            cancelBtn.Text = "Cancel";
            cancelBtn.Location = new Point(saveBtn.Width, tb.Height);
            cancelBtn.UseVisualStyleBackColor = true;
            inputForm.Width = tb.Width;
            inputForm.Controls.Add(tb);
            inputForm.Controls.Add(saveBtn);
            inputForm.Controls.Add(cancelBtn);

            //some anonymous button listeners
            saveBtn.Click += (s1, e1) => {
                if (tb.Text.Length != 0) 
                    OnSaveButtonClickEvent.Invoke(tb.Text);
                this.Controls.Remove(inputForm);
                inputForm.Dispose();
                this.saveButton.Show();
                this.LoadButton.Show();
                this.Invoke(gr, 8, 2);
            };

            cancelBtn.Click += (s1, e1) => {
                this.Controls.Remove(inputForm);
                inputForm.Dispose();
                this.gridPanel.Show();
                this.saveButton.Show();
                this.LoadButton.Show();
                this.Invoke(gr, 8, 2);
                this.SetGridDimension(_columns, _rows);
            };

            this.Invoke(gr,8,-2); // all ready, size the grid down
            this.saveButton.Hide();
            this.LoadButton.Hide();
            this.Controls.Add(inputForm);
        }

        //Used for save button graphics due to threads
        public delegate void GridResizer(int pixels, int byPixel);

        private void ResizeGrid(int times, int byPixel) {
            int count = 0;
            while (count < times) {
                ChangeGrid(byPixel);
                count++;
            }
            SetGridDimension(_columns, _rows);
        }

        private void ChangeGrid(int i) {
            gridPanel.Width += i;
            gridPanel.Height += i;
            gridPanel.Location = new Point(gridPanel.Location.X + ((i / 2) * -1), gridPanel.Location.Y + (i * -1));
        }

        public void DisplayChannel(Channel channel) {
            ChannelComboBox.SelectedItem = channel;
        }

        public void SetVolume(int Volume){
            this.VolumeHscrollbar.Value = Volume;
        }

        private void channelComboBoxChanged(object sender, EventArgs e) {
            ComboBox cb = (ComboBox)sender;
            Channel selectedChannel = (Channel)cb.SelectedItem;
            OnChannelChangeEvent.Invoke(selectedChannel);
        }

        private void CaComboBoxChanged(object sender, EventArgs e) {
            ComboBox cb = (ComboBox)sender;
            CAType selectedType = (CAType)cb.SelectedItem;
            OnCATypeChangeEvent.Invoke(selectedType);
        }

        private void MapperChanged(object sender, EventArgs e)
        {
            ComboBox cb = (ComboBox) sender;
            Mapper m = (Mapper) cb.SelectedItem;
            OnMapperChangeEvent.Invoke(m);
        }

        private void InputModeChanged(object sender, EventArgs e) {
            ComboBox cb = (ComboBox)sender;
            InputMode inputMode = (InputMode)cb.SelectedItem;
            OnInputModeChangeEvent.Invoke(inputMode);
        }

        //Also called on MouseDown
        private void RewindButtonClick(object sender, EventArgs e) {
            OnRewindButtonClickEvent.Invoke();
        }

        //Also called on MouseDown
        private void ForwardButtonClick(object sender, EventArgs e) {
            OnForwardButtonClickEvent.Invoke();
        }

        /*******************************************************************
         *          Trivial - delegate methods implemented from IView
         * *****************************************************************/

        public List<GameOfLife.GofPattern> Patterns { get; set; }

        public Action<GameOfLife.GofPattern, int, int> OnApplyCaPatternEvent { get; set; }

        public Action OnOkButtonClickEvent { get; set; }

        public Action OnPlayButtonClickEvent { get; set; }

        public Action OnRewindButtonClickEvent { get; set; }

        public Action OnForwardButtonClickEvent { get; set; }

        public Action OnPauseButtonClickEvent { get; set; }

        public Action<int, int> OnCellClickEvent { get; set; }

        public Action<int> OnVolumeChangeEvent { get; set; }

        public Action<Channel> OnChannelChangeEvent { get; set; }

        public Action<InputMode> OnInputModeChangeEvent { get; set; }

        public Action<CAType> OnCATypeChangeEvent { get; set; }

        public Action<Mapper> OnMapperChangeEvent { get; set; }

        public Action<String> OnSaveButtonClickEvent { get; set; }

        public Action<String> OnLoadButtonClickEvent { get; set; }
    }
}
